package com.yiji.fintech.musfinance.common.utils.object;

import com.acooly.openapi.framework.common.annotation.OpenApiAlias;
import com.acooly.openapi.framework.common.convert.ApiServiceConversionService;
import com.acooly.openapi.framework.common.enums.ApiServiceResultCode;
import com.acooly.openapi.framework.common.exception.ApiServiceException;
import com.acooly.openapi.framework.common.utils.json.JsonMarshallor;
import com.alibaba.fastjson.JSONException;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;
import com.yiji.fintech.musfinance.common.exception.ObjectParamFormatException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
@Slf4j
public class GatewayObjectAccessor<T> {

	private final T target;
	private static ConversionService conversionService = ApiServiceConversionService.INSTANCE;

	private static JsonMarshallor jsonMarshallor = JsonMarshallor.INSTANCE;

	private Map<String, Field> fieldMap;
	private final static ConcurrentMap<Class<?>, Map<String, Field>> CLASS_MAP = Maps.newConcurrentMap();

	/**
	 * 忽略transient字段
	 */
	private final static Predicate<Field> TRANSIENT_DENY_PREDICATE = new Predicate<Field>() {
		@Override
		public boolean apply(Field input) {
			if (input == null) {
				return false;
			}

			return !Modifier.isTransient(input.getModifiers());
		}
	};
	/**
	 * 忽略transient字段
	 */
	private final static Predicate<Field> ALL_ACCEPT_PREDICATE = new Predicate<Field>() {
		@Override
		public boolean apply(Field input) {
			return true;
		}
	};

	public static <T> GatewayObjectAccessor<T> of(T target) {
		return new GatewayObjectAccessor<T>(target);
	}

	private GatewayObjectAccessor(T target) {
		Assert.notNull(target, "Target object must not be null");
		this.target = target;
		Map<String, Field> fieldMap = CLASS_MAP.get(target.getClass());
		if (fieldMap == null) {
			final Map<String, Field> tmpMap = Maps.newHashMap();
			ReflectionUtils.doWithFields(this.target.getClass(), new ReflectionUtils.FieldCallback() {
				@Override
                public void doWith(Field field) {
					if (tmpMap.containsKey(field.getName())) {
						throw new ApiServiceException(ApiServiceResultCode.FIELD_NOT_UNIQUE,
								field + "和" + tmpMap.get(field.getName()) + "重名");
					} else {
						tmpMap.put(field.getName(), field);
					}
				}
			});
			CLASS_MAP.put(target.getClass(), tmpMap);
			fieldMap = tmpMap;
		}
		this.fieldMap = fieldMap;
	}

	public String getFieldAlias(String property) {
		Field field = this.fieldMap.get(property);
		if (field == null) {
			return property;
		}
		OpenApiAlias openApiAlias = field.getAnnotation(OpenApiAlias.class);
		return (openApiAlias != null && Strings.isNullOrEmpty(openApiAlias.value())) ? openApiAlias.value() : property;
	}

	/**
	 * 获取对象属性名集合
	 *
	 * @return
	 */
	public Set<String> propertySet() {
		return this.fieldMap.keySet();
	}

	/**
	 * 获取非transient属性名集合
	 *
	 * @return
	 */
	public Set<String> propertySetExcludeTransient() {
		Set<String> fieldSet = Sets.newHashSet();
		for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
			if (!Modifier.isTransient(entry.getValue().getModifiers())) {
				fieldSet.add(entry.getKey());
			}
		}
		return fieldSet;
	}

	/**
	 * 获取属性字符串 如果属性标注OpenApiField.type=JSON,则设置json字符串
	 *
	 * @param propertyName
	 * @return
	 * @throws BeansException
	 */
	public String getPropertyValue(String propertyName) throws BeansException {
		Field field = this.fieldMap.get(propertyName);
		if (field == null) {
			return null;
		}
		Object obj = null;
		try {
			ReflectionUtils.makeAccessible(field);
			obj = field.get(this.target);
			if (obj == null) {
				return null;
			}
			if (isCollection(field)) {
				return jsonMarshallor.marshall(obj);
			}
			// 处理复杂类型会报错
			return conversionService.convert(obj, String.class);
		} catch (IllegalArgumentException | IllegalAccessException ex) {
			log.error("属性{}读取失败", field.getType(), ex);
			throw new ApiServiceException(ApiServiceResultCode.INTERNAL_ERROR, "property=" + propertyName + "读取失败");
		} catch (ConverterNotFoundException ex) {
			return jsonMarshallor.marshall(obj);
		} catch (Exception ex) {
			log.error("读取属性失败", ex);
			throw new ApiServiceException(ApiServiceResultCode.INTERNAL_ERROR, "property=" + propertyName + "读取失败");
		}
	}

	/**
	 * 设置属性 如果属性标注OpenApiField.type=JSON,则设置json字符串
	 *
	 * @param propertyName
	 * @param newValue
	 */
	public void setPropertyValue(String propertyName, Object newValue) {
		Field field = this.fieldMap.get(propertyName);
		if (field == null || newValue == null) {
			return;
		}
		try {
			ReflectionUtils.makeAccessible(field);
			if (isCollection(field)) {
				parse((String) newValue, field);
				return;
			} else {
				Object convertedValue = conversionService.convert(newValue, field.getType());
				field.set(this.target, convertedValue);
			}

		} catch (IllegalArgumentException | IllegalAccessException ex) {
			log.error("属性{}设置失败", field.getType(), ex);
			throw new ObjectParamFormatException(field.getName(), newValue, field.getType().getSimpleName());
		} catch (ConverterNotFoundException ex) {
			parse((String) newValue, field);
		} catch (ConversionFailedException cfe) {
			String enumOptions = null;
			if (field.getType().isEnum()) {
				// 如果枚举值为空,忽略
				if (Strings.isNullOrEmpty(newValue.toString())) {
					return;
				}

				Object[] objects = field.getType().getEnumConstants();
				enumOptions = Arrays.toString(objects);
			} else {
				log.error("属性{}设置失败", field.getType(), cfe);
			}
			throw new ObjectParamFormatException(field.getName(), newValue,
					field.getType().getSimpleName() + (enumOptions != null ? enumOptions : ""));
		} catch (ObjectParamFormatException ex) {
			throw ex;
		} catch (Exception ex) {
			log.error("属性{}设置失败,值={}", field.toString(), newValue);
			throw new ObjectParamFormatException(field.getName(), newValue, field.getType().getSimpleName());
		}
	}

	private boolean isCollection(Field field) {
		return List.class.isAssignableFrom(field.getType()) || Map.class.isAssignableFrom(field.getType())
				|| Set.class.isAssignableFrom(field.getType()) || field.getType().isArray();
	}

	private boolean isJSONObject(Field field) {
		Class type = field.getType();
		return Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type) || type.isArray()
				|| Number.class.isAssignableFrom(type) || type.isPrimitive() || Boolean.class.isAssignableFrom(type);
	}

	/**
	 * 获取对象数据,忽略transient字段,复杂对象转换为json
	 *
	 * @return
	 */
	public Map<String, String> getAllDataExcludeTransient() {
		return getAllData(TRANSIENT_DENY_PREDICATE);
	}

	public Map<String, Object> getMarshallData() {
		Assert.notNull(target, "predicate must not be null");
		Map<String, Object> dataMap = Maps.newTreeMap();
		for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
			if (!TRANSIENT_DENY_PREDICATE.apply(entry.getValue())) {
				continue;
			}
			Object value = this.getMarshallValue(entry.getKey());
			if (value != null) {
				dataMap.put(entry.getKey(), value);
			}
		}
		return dataMap;
	}

	public Object getMarshallValue(String propertyName) throws BeansException {
		Field field = this.fieldMap.get(propertyName);
		if (field == null) {
			return null;
		}
		Object obj = null;
		try {
			ReflectionUtils.makeAccessible(field);
			obj = field.get(this.target);
			if (obj == null) {
				return null;
			} else if (obj instanceof String || isJSONObject(field)) {
				return obj;
			} else if (isCollection(field)) {
				return jsonMarshallor.marshall(obj);
			}
			// 处理复杂类型会报错
			return conversionService.convert(obj, String.class);
		} catch (IllegalArgumentException | IllegalAccessException ex) {
			log.error("属性{}读取失败", field.getType(), ex);
			throw new ApiServiceException(ApiServiceResultCode.INTERNAL_ERROR, "property=" + propertyName + "读取失败");
		} catch (ConverterNotFoundException ex) {
			return jsonMarshallor.marshall(obj);
		} catch (Exception ex) {
			log.error("读取属性失败", ex);
			throw new ApiServiceException(ApiServiceResultCode.INTERNAL_ERROR, "property=" + propertyName + "读取失败");
		}
	}

	/**
	 * 获取对象所有属性,复杂对象转换为json
	 *
	 * @param predicate
	 * @return
	 */
	public Map<String, String> getAllData(Predicate<Field> predicate) {
		Assert.notNull(target, "predicate must not be null");
		Map<String, String> dataMap = Maps.newTreeMap();
		for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
			if (!predicate.apply(entry.getValue())) {
				continue;
			}
			String value = this.getPropertyValue(entry.getKey());
			if (value != null) {
				dataMap.put(entry.getKey(), value);
			}
		}
		return dataMap;
	}

	/**
	 * 为了json转换,需要把某些对象保留类型.
	 *
	 * @param
	 * @return 获得此map后, 在通过json Marshall 输出.
	 */
	public Map<String, Object> getAllDataExcludeTransientForJsonProcess() {
		return getAllDataForJsonProcess(TRANSIENT_DENY_PREDICATE);
	}

	public Map<String, Object> getAllDataForJsonProcess(Predicate<Field> predicate) {
		Assert.notNull(target, "predicate must not be null");
		Map<String, Object> dataMap = Maps.newHashMap();
		for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
			if (!predicate.apply(entry.getValue())) {
				continue;
			}

			Field field = entry.getValue();
			Object obj = null;
			try {
				ReflectionUtils.makeAccessible(field);
				obj = field.get(this.target);
				if (obj == null) {
					continue;
				}
				if (obj instanceof String) {
					dataMap.put(entry.getKey(), obj);
					continue;
				} else if (isJSONObject(field)) {
					dataMap.put(entry.getKey(), obj);
					continue;
				}
				String value = conversionService.convert(obj, String.class);
				dataMap.put(entry.getKey(), value);
			} catch (IllegalArgumentException | IllegalAccessException ex) {
				log.error("属性{}读取失败", field.getType(), ex);
			} catch (ConverterNotFoundException ex) {
				dataMap.put(entry.getKey(), obj);
			} catch (Exception ex) {
				log.error("读取属性失败", ex);
			}
		}
		return dataMap;
	}

	/**
	 * 获取对象所有属性,复杂对象转换为json
	 *
	 * @return
	 */
	public Map<String, String> getAllData() {
		return getAllData(ALL_ACCEPT_PREDICATE);
	}

	private void parse(String newValue, Field field) {
		try {
			TypeToken typeToken = TypeToken.of(field.getGenericType());
			Object convertedValue = jsonMarshallor.parse(newValue, typeToken.getType());
			field.set(this.target, convertedValue);
		} catch (Exception e) {
			log.error("属性{}设置失败,值={},原因={}", field.toString(), newValue, e.getMessage());
			if (e instanceof JSONException) {
				checkEnumConvertException(e, newValue, field);
				Throwable cause = e.getCause();
				if (cause instanceof ObjectParamFormatException) {
					ObjectParamFormatException ex = (ObjectParamFormatException) cause;
					String requiredTypeOrFormat = ex.getRequiredTypeOrFormat();
					throw new ObjectParamFormatException(field.getName(), newValue,
							field.getType().getSimpleName() + "." + requiredTypeOrFormat);
				}
			}
			throw new ObjectParamFormatException(field.getName(), newValue, field.getType().getSimpleName());
		}

	}

	private void checkEnumConvertException(Exception e, String newValue, Field field) {
		String message = e.getMessage();
		if (message != null && message.indexOf("enum") > -1) {
			String noExists = "No enum constant";
			// 解析 com.alibaba.fastjson.JSONException: No enum constant
			// com.yjf.common.payengine.enums.BankCardTypeEnum.DEBIT
			if (message.contains(noExists)) {
				String msg = "枚举类型:"
						+ message.substring(message.indexOf(noExists) + noExists.length(), message.length()) + "不存在";
				throw new ObjectParamFormatException(field.getName(), newValue, msg);
			}
			String parseError = "parse enum";
			String valueStr = ", value : ";
			// 解析 parse enum com.yjf.common.payengine.enums.BankCodeEnum error,
			// value : 123
			if (message.contains(parseError)) {
				String enumName = message.substring(message.indexOf(parseError) + 1 + parseError.length(),
						message.indexOf("error") - 1);
				String value = message.substring(message.indexOf(valueStr) + valueStr.length());
				String msg = "枚举类型:" + enumName + " 值:" + value + "不存在";
				throw new ObjectParamFormatException(field.getName(), newValue, msg);
			}
		}

	}
}
